package main;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;

import panels.ProgressPanel;
import tasks.Reportable;
import tasks.TaskList;
import threads.FileRefresher;
import threads.TaskGenerator;
import threads.TaskRemover;
import threads.TaskWorker;
import tree.FileDateNode;
import tree.FileNumberNode;
import tree.GameNode;
import tree.MyTreeCellRenderer;
import tree.PathNode;

public class Interface implements ActionListener, DropTargetListener, Reportable{
	
	private JFrame f;
	private JSplitPane split;
	private JPanel synclist;
	private JLabel empty;
	private JToolBar bar;
	
	private JToggleButton toggle;
	private final Icon tog1;
	private final Icon tog2;
	
	private JTree tree;
	private DefaultMutableTreeNode 	root;
	private DefaultTreeModel model;
	
	private GameDialog diag;
	
	private HashMap<String, Game> games;
	
	private ConcurrentLinkedQueue<TaskList> syncs;
	
	private TaskWorker[] threads;
	
	private AtomicBoolean dragEnabled;
	private boolean isEmpty;
	
	public Interface(){
		try {
			UIManager.setLookAndFeel( "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel" );
		} catch (Exception e) {}
		f = new JFrame("Save Manager");
		f.setSize(600, 300);
		Dimension screen = f.getToolkit().getScreenSize();
		f.setLocation((screen.width-f.getWidth())/2, 
				(screen.height-f.getHeight())/2);
		f.setIconImage(new ImageIcon(getClass().getResource("/small/target.png")).getImage());
		f.setLayout(new BorderLayout());
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		f.setMinimumSize(new Dimension(500,200));
		dragEnabled = new AtomicBoolean();
		dragEnabled.set(true);
		
		root = new DefaultMutableTreeNode("Games");
		model = new DefaultTreeModel(root);
		tree = new JTree(model);
		tree.setCellRenderer(new MyTreeCellRenderer());
		tree.setDragEnabled(true);
		tree.setBackground(new Color(214,217,223));

		JScrollPane scroll1 = new JScrollPane( JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, 
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED ); 
		scroll1.setViewportView(tree);
		scroll1.getVerticalScrollBar().setUnitIncrement(16);
		scroll1.getHorizontalScrollBar().setUnitIncrement(16);
		
		synclist = new JPanel();
		synclist.setFocusable(true);
		synclist.setLayout(new BoxLayout(synclist, BoxLayout.Y_AXIS));
		synclist.setDropTarget(new DropTarget(synclist, this));
		JScrollPane scroll2 = new JScrollPane( JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, 
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER ); 
		scroll2.setViewportView(synclist);
		scroll2.getVerticalScrollBar().setUnitIncrement(16);
		scroll2.getHorizontalScrollBar().setUnitIncrement(16);
		
		split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, scroll1, scroll2);
		split.setDividerSize(5);
		split.setOneTouchExpandable(true);
		f.add(split, BorderLayout.CENTER);
		
		bar = new JToolBar("Icons");
		
		JButton btn = new JButton(new ImageIcon(getClass().getResource("/big/add.png")));
		btn.setActionCommand("add");
		btn.addActionListener(this);
		bar.add(btn);
		
		btn = new JButton(new ImageIcon(getClass().getResource("/big/remove.png")));
		btn.setActionCommand("remove");
		btn.addActionListener(this);
		bar.add(btn);
		
		btn = new JButton(new ImageIcon(getClass().getResource("/big/refresh.png")));
		btn.setActionCommand("refresh");
		btn.addActionListener(this);
		bar.add(btn);
		
		bar.addSeparator(new Dimension(24,24));
		
		btn = new JButton(new ImageIcon(getClass().getResource("/big/arrow_down.png")));
		btn.setActionCommand("download");
		btn.addActionListener(this);
		bar.add(btn);
		
		btn = new JButton(new ImageIcon(getClass().getResource("/big/arrow_up.png")));
		btn.setActionCommand("upload");
		btn.addActionListener(this);
		bar.add(btn);
		
		bar.addSeparator(new Dimension(24,24));
		
		tog1 = new ImageIcon(getClass().getResource("/big/play.png"));
		tog2 = new ImageIcon(getClass().getResource("/big/pause.png"));
		toggle = new JToggleButton(tog1, false);
		toggle.setActionCommand("play");
		toggle.addActionListener(this);
		bar.add(toggle);
				
		bar.addSeparator(new Dimension(24,24));
		
		btn = new JButton(new ImageIcon(getClass().getResource("/big/tools.png")));
		btn.setActionCommand("settings");
		btn.addActionListener(this);
		bar.add(btn);
		
		f.add(bar, BorderLayout.NORTH);
		
		diag = new GameDialog(f, this);
		games = new HashMap<String, Game>();
		syncs = new ConcurrentLinkedQueue<TaskList>();
		
		empty = new JLabel("Please add a game!");
		if(synclist.getComponentCount()==0){
			isEmpty = true;
			synclist.add(empty);
		}
		
		f.setVisible(true);
		split.setDividerLocation(0.3);
		
		threads = new TaskWorker[Constants.THREAD_COUNT];
		for(int i=0; i<threads.length; i++){
			threads[i] = new TaskWorker(syncs, this);
			threads[i].start();
		}
	}
	
	public JFrame getFrame(){
		return f;
	}

	@SuppressWarnings("unchecked")
	public void addGame(String name, String absPath, long biggestTime,
			List<GameFile> gamefiles){
		if(games.containsKey(name)) return;
		
		GameNode node = new GameNode(name);
		node.add(new PathNode(absPath));
		DateFormat simple = new SimpleDateFormat();
		node.add(new FileDateNode(simple.format(biggestTime)));
		node.add(new FileNumberNode(gamefiles.size()+" Files"));

		//find position to add
		Enumeration<DefaultMutableTreeNode> children = root.children();
		int i = 0;
		boolean inserted = false;
		while(children.hasMoreElements()){
			DefaultMutableTreeNode n = children.nextElement();
			String gameName = n.getUserObject().toString();
			if(gameName.compareTo(name)>=0){
				root.insert(node, i);
				inserted = true;
				break;
			}
			i++;
		}
		if(!inserted) root.add(node);
		
		Game g = new Game(name, absPath, node, gamefiles);
		
		games.put(name, g);

		model.reload();
		tree.repaint();
	}
	
	public void addGameToSync(String game){
		if(games.containsKey(game)){
			Game g = games.get(game);
			ProgressPanel pro = g.getPro();
			if(pro==null){
				pro = new ProgressPanel(this, g);
				try {
					new TaskGenerator(syncs, g.clone()).start();
				} catch (Exception e) {
					e.printStackTrace();
				}
				g.setPro(pro);
				if(isEmpty){
					synclist.removeAll();
					isEmpty = false;
				}
				synclist.add(pro);
				f.validate();
			}
		}
	}
	
	public List<GameFile> generateFileList(String dirname){
		if(dirname==null) return new ArrayList<GameFile>();
		File startdir = new File(dirname);
		if(startdir.exists()) {
			ArrayList<GameFile> gamefiles = new ArrayList<GameFile>();
			long biggestTime = 0;
			
			//add files to filelist
			Stack<File> dirs = new Stack<File>();
			dirs.push(startdir);
			
			while(dirs.size()>0){
				File[] ff = dirs.pop().listFiles();
				if(ff!=null){
					for(File f : ff){
						if(f.isDirectory()) dirs.push(f);
						else {
							String path = f.getAbsolutePath();
							path = path.substring(dirname.length(), path.length());
							long timestamp = f.lastModified();
							long size = f.length();
							if(timestamp>biggestTime) biggestTime = timestamp;
						
							gamefiles.add(new GameFile(path, timestamp, size));
						}
					}
				}
			}
			return gamefiles;
		}
		return new ArrayList<GameFile>();
	}
	
	public long biggestTimestamp(List<GameFile> files){
		if(files==null) return -1;
		long timestamp = 0;
		for(int i=0; i<files.size(); i++){
			GameFile g = files.get(i);
			if(g!=null){
				if(g.getModDate()>timestamp) timestamp = g.getModDate();
			}
		}
		return timestamp;
	}
	
	public void removeGameFromSync(String name){
		if(games.containsKey(name)){
			Game g = games.get(name);
			ProgressPanel pro = g.getPro();
			if(pro!=null) {
				synclist.remove(pro);
				if(synclist.getComponentCount()==0){
					isEmpty = true;
					synclist.add(empty);
				}
				synclist.repaint();
				f.validate();
				new TaskRemover(syncs, g.getGame()).start();
				g.setPro(null);
			}
		}
	}
	
	public void removeGame(String name){
		if(games.containsKey(name)){
			this.removeGameFromSync(name);
			games.remove(name);
		}
	}

	public static void main(String[] args) {
		new Interface();
	}

	@Override
	public void actionPerformed(ActionEvent ev) {
		String command = ev.getActionCommand();
		if(command.equalsIgnoreCase("add")){
			diag.clear();
			disableBar(false);
			diag.setVisible(true);
		}
		else if(command.equalsIgnoreCase("remove")){
			TreePath[] paths = tree.getSelectionPaths();
			if(paths!=null){
				this.disableBar(true);
				for(TreePath path : paths){
					Object obj = path.getLastPathComponent();
					if(obj instanceof DefaultMutableTreeNode){
						DefaultMutableTreeNode node = 
							(DefaultMutableTreeNode) obj;
						if(node.getLevel()==1) {
							String game = node.getUserObject().toString();
							model.removeNodeFromParent(node);
							this.removeGame(game);
						}
					}
				}
				this.enableBar();
			}
		}else if(command.equalsIgnoreCase("play")){
			if(toggle.isSelected()){
				toggle.setIcon(tog2);
				for(int i=0; i<threads.length; i++){
					threads[i].startAgain();
				}
			}else{
				toggle.setIcon(tog1);
				for(int i=0; i<threads.length; i++){
					threads[i].pause();
				}
			}
		}else if(command.equalsIgnoreCase("settings")){
			JOptionPane.showMessageDialog(f, "Settings!");
		}else if(command.equalsIgnoreCase("download")){
			JOptionPane.showMessageDialog(f, "Download!");
		}else if(command.equalsIgnoreCase("upload")){
			JOptionPane.showMessageDialog(f, "Upload!");
		}else if(command.equalsIgnoreCase("refresh")){
			this.disableBar(true);
			Iterator<String> it = games.keySet().iterator();
			List<String> gamelist = new ArrayList<String>();
			while(it.hasNext()) gamelist.add(it.next());
			new FileRefresher(this, gamelist).start();
		}
	}

	public void disableBar(boolean waitCursor) {
		if(waitCursor) f.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
		this.dragEnabled.set(false);
		Component[] comps = bar.getComponents();
		for(Component c : comps){
			if(c!=null) c.setEnabled(false);
		}
	}
	
	public void enableBar() {
		f.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
		this.dragEnabled.set(true);
		Component[] comps = bar.getComponents();
		for(Component c : comps){
			if(c!=null) c.setEnabled(true);
		}
	}

	@Override
	public void dragEnter(DropTargetDragEvent ev) {
		try {
			Transferable tr = ev.getTransferable();
			String game = tr.getTransferData(DataFlavor.stringFlavor).toString();
			if(!dragEnabled.get()||!games.containsKey(game)
					||games.get(game).getPro()!=null) ev.rejectDrag(); 
		} catch (Exception e){}
	}

	@Override
	public void dragExit(DropTargetEvent arg0) {}

	@Override
	public void dragOver(DropTargetDragEvent ev) {}

	@Override
	public void drop(DropTargetDropEvent ev) {
		try {
			Transferable tr = ev.getTransferable();
			String game = tr.getTransferData(DataFlavor.stringFlavor).toString();
			this.addGameToSync(game);
			ev.dropComplete(true);
		} catch (Exception e){}
	}

	@Override
	public void dropActionChanged(DropTargetDragEvent arg0) {}

	@Override
	public void reportFileDone(String game, String relPath, long size) {
		if(games.containsKey(game)){
			ProgressPanel pro = games.get(game).getPro();
			if(pro!=null){
				pro.addDoneFile(size);
			}
		}		
	}

	@Override
	public void reportFileProgress(String game, 
			final String relPath, final long progress) {
		if(games.containsKey(game)){
			final ProgressPanel pro = games.get(game).getPro();
			if(pro!=null){
				pro.setFileProgress(relPath, progress);
			}
		}
	}

	@Override
	public void reportGameDone(final String game) {
		if(games.containsKey(game)){
			final ProgressPanel pro = games.get(game).getPro();
			if(pro!=null){
				EventQueue.invokeLater(new Runnable(){
					@Override
					public void run() {
						pro.setGameDone();
						disableBar(true);
						updateFileList(game);
						enableBar();
					}
				});
			}
		}			
	}

	public void updateFileList(String game) {
		if(games.containsKey(game)){
			Game g = games.get(game);
			List<GameFile> files = this.generateFileList(g.getAbsPath());
			g.setFiles(files);
			//update node
			GameNode n = g.getNode();
			if(n.getChildCount()==3){
				n.removeAllChildren();
				n.add(new PathNode(g.getAbsPath()));
				DateFormat simple = new SimpleDateFormat();
				n.add(new FileDateNode(simple.format(this.biggestTimestamp(files))));
				n.add(new FileNumberNode(files.size()+" Files"));
				
				model.reload();
			}
		}
	}

	@Override
	public void reportGameStart(String game, long maxSize) {
		if(games.containsKey(game)){
			ProgressPanel pro = games.get(game).getPro();
			if(pro!=null){
				pro.setMaxSize(maxSize);
			}
		}
	}

	@Override
	public void reportFileStart(String game, final String relPath, 
			final long maxSize) {
		if(games.containsKey(game)){
			final ProgressPanel pro = games.get(game).getPro();
			if(pro!=null){
				EventQueue.invokeLater(new Runnable(){
					@Override
					public void run() {
						pro.setNewFile(relPath, maxSize);
					}
				});
			}
		}
	}

}
